#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <ringlib.h>
#include <kernel.h>
#include <kbd.h>
#include <vga.h>

#define KBD_BFR_SIZE        16

#define KBD_CTRL_BIT        (1 << 0)
#define KBD_ALT_BIT         (1 << 1)
#define KBD_SHIFT_BIT       (1 << 2)

#define KBD_LED_NUM         (1 << 0)
#define KBD_LED_CAPS        (1 << 1)
#define KBD_LED_SCROLL      (1 << 2)

#define KBD_IRQ             1
#define KBD_INT_NUM         IA_80x86_MI_BASE_NUM + KBD_IRQ

#define KBD_8042_PORT1      0x60
#define KBD_8042_PORT2      0x64

#define KBD_8042_OUT_F      (1 << 0)
#define KBD_8042_IN_F       (1 << 1)

#define KBD_8042_DISABLE    0xAD
#define KBD_8042_ENABLE     0xAE

#define KBD_8048_SETLED     0xED

/* key ascii table */

#define KEY_F1              0
#define KEY_F2              0
#define KEY_F3              0
#define KEY_F4              0
#define KEY_F5              0
#define KEY_F6              0
#define KEY_F7              0
#define KEY_F8              0
#define KEY_F9              0
#define KEY_F10             0
#define KEY_F11             0
#define KEY_F12             0

#define KEY_L_CTRL          0xFE
#define KEY_R_CTRL          0xFD
#define KEY_L_ALT           0xFC
#define KEY_R_ALT           0xFB
#define KEY_L_SHIFT         0xFA
#define KEY_R_SHIFT         0xF9

#define KEY_CAPS_LOCK       0xF8
#define KEY_NUM_LOCK        0xF7
#define KEY_SCR_LOCK        0xF6

#define KEY_L_WIN           0
#define KEY_R_WIN           0
#define KEY_POP             0

#define KEY_POWER           0
#define KEY_SLEEP           0
#define KEY_WAKE            0

#define KEY_ESC             27
#define KEY_BACKSPACE       9
#define KEY_TAB             8
#define KEY_ENTER           13
#define KEY_NUM_ENTER       13
#define KEY_NUM_ADD         '+'
#define KEY_NUM_SUB         '-'
#define KEY_NUM_MUL         '*'
#define KEY_NUM_DIV         '/'

#define KEY_HOME            0
#define KEY_INSERT          0
#define KEY_DEL             0
#define KEY_END             0
#define KEY_PG_UP           0
#define KEY_PG_DOWN         0

#define KEY_UP              0
#define KEY_DOWN            0
#define KEY_LEFT            0
#define KEY_RIGHT           0

#define KEY_NO_ASC          0

#define KEY_PRN_SCR_VK      0

#define KEY_NONE            0xFF

#define KEY_MAP_SCAN_SIZE   0x60
#define KEY_MAP_COUNT_SIZE  4

#define KEY_MAP_SHIFT_INDEX 2

static int kbd_buf_scn[KBD_BFR_SIZE + 1];
static unsigned char kbd_buf_asc[KBD_BFR_SIZE + 1];
static unsigned char tscan[KBD_BFR_SIZE];

static ring kbd_asc_rng;
static ring kbd_scn_rng;

static volatile char kbd_flag;

static volatile char kbd_led_flag;

volatile _Bool  kbd_bfr_ready;

extern EOI_FUNC eoi_ptr;

static char US_MAP[KEY_MAP_COUNT_SIZE][KEY_MAP_SCAN_SIZE] = {
    {
/* 0x0_ */  KEY_NONE,       KEY_ESC,        '1',            '2',
            '3',            '4',            '5',            '6',
            '7',            '8',            '9',            '0',
            '-',            '=',            KEY_BACKSPACE,  KEY_TAB,
/* 0x1_ */  'q',            'w',            'e',            'r',
            't',            'y',            'u',            'i',
            'o',            'p',            '[',            ']',
            KEY_ENTER,      KEY_L_CTRL,     'a',            's',
/* 0x2_ */  'd',            'f',            'g',            'h',
            'j',            'k',            'l',            ';',
            '\'',           '`',            KEY_L_SHIFT,    '\\',
            'z',            'x',            'c',            'v',
/* 0x3_ */  'b',            'n',            'm',            ',',
            '.',            '/',            KEY_R_SHIFT,    KEY_NUM_MUL,
            KEY_L_ALT,      ' ',            KEY_CAPS_LOCK,  KEY_F1,
            KEY_F2,         KEY_F3,         KEY_F4,         KEY_F5,
/* 0x4_ */  KEY_F6,         KEY_F7,         KEY_F8,         KEY_F9,
            KEY_F10,        KEY_NUM_LOCK,   KEY_SCR_LOCK,   '7' /* num */,
            '8' /* num */,  '9' /* num */,  KEY_NUM_SUB,    '4' /* num */,
            '5' /* num */,  '6' /* num */,  KEY_NUM_ADD,    '1' /* num */,
/* 0x5_ */  '2' /* num */,  '3' /* num */,  '0' /* num */,  '.' /* num */,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_F11,
            KEY_F12,        KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
    },
    /* E0 */
    {
/* 0x0_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
/* 0x1_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NUM_ENTER,  KEY_R_CTRL,     KEY_NONE,       KEY_NONE,
/* 0x2_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
/* 0x3_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NUM_DIV,    KEY_NONE,       KEY_PRN_SCR_VK,
            KEY_R_ALT,      KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
/* 0x4_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_HOME,
            KEY_UP,         KEY_PG_UP,      KEY_NONE,       KEY_LEFT,
            KEY_NONE,       KEY_RIGHT,      KEY_NONE,       KEY_END,
/* 0x5_ */  KEY_DOWN,       KEY_PG_DOWN,    KEY_INSERT,     KEY_DEL,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_L_WIN,
            KEY_R_WIN,      KEY_POP,        KEY_POWER,      KEY_SLEEP,
    },
    /* shift mask */
    {
/* 0x0_ */  KEY_NONE,       KEY_ESC,        '!',            '@',
            '#',            '$',            '%',            '^',
            '&',            '*',            '(',            ')',
            '_',            '+',            KEY_BACKSPACE,  KEY_TAB,
/* 0x1_ */  'Q',            'W',            'E',            'R',
            'T',            'Y',            'U',            'I',
            'O',            'P',            '{',            '}',
            KEY_ENTER,      KEY_L_CTRL,     'A',            'S',
/* 0x2_ */  'D',            'F',            'G',            'H',
            'J',            'K',            'L',            ':',
            '\"',           '~',            KEY_L_SHIFT,    '|',
            'Z',            'X',            'C',            'V',
/* 0x3_ */  'B',            'N',            'M',            '<',
            '>',            '?',            KEY_R_SHIFT,    KEY_NUM_MUL,
            KEY_L_ALT,      ' ',            KEY_CAPS_LOCK,  KEY_F1,
            KEY_F2,         KEY_F3,         KEY_F4,         KEY_F5,
/* 0x4_ */  KEY_F6,         KEY_F7,         KEY_F8,         KEY_F9,
            KEY_F10,        KEY_NUM_LOCK,   KEY_SCR_LOCK,   KEY_HOME,
            KEY_UP,         KEY_PG_UP,      KEY_NUM_SUB,    KEY_LEFT,
            KEY_NONE,       KEY_RIGHT,      KEY_NUM_ADD,    KEY_END,
/* 0x5_ */  KEY_DOWN,       KEY_PG_DOWN,    KEY_INSERT,     KEY_DEL,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_F11,
            KEY_F12,        KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
    },
    /* shift E0 */
    {
/* 0x0_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
/* 0x1_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NUM_ENTER,  KEY_R_CTRL,     KEY_NONE,       KEY_NONE,
/* 0x2_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
/* 0x3_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NUM_DIV,    KEY_NONE,       KEY_PRN_SCR_VK,
            KEY_R_ALT,      KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
/* 0x4_ */  KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_HOME,
            KEY_UP,         KEY_PG_UP,      KEY_NONE,       KEY_LEFT,
            KEY_NONE,       KEY_RIGHT,      KEY_NONE,       KEY_END,
/* 0x5_ */  KEY_DOWN,       KEY_PG_DOWN,    KEY_INSERT,     KEY_DEL,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_NONE,
            KEY_NONE,       KEY_NONE,       KEY_NONE,       KEY_L_WIN,
            KEY_R_WIN,      KEY_POP,        KEY_POWER,      KEY_SLEEP,
    },
};

unsigned char num_map[12] = {
    KEY_DEL,    0 /* never used */, KEY_INSERT, KEY_END,
    KEY_DOWN,   KEY_PG_DOWN,        KEY_LEFT,   KEY_NONE,
    KEY_RIGHT,  KEY_HOME,           KEY_UP,     KEY_PG_UP,
};

#define KEY_VSCAN_BREAK     0x1DE1

#define KEY_VSCAN_PR_SCRN   0x2AE0

/*
    special key
            Make                Break
    Pr Scrn E0 2A E0 37         E0 B7 E0 AA
    Pause   E1 1D 45 E1 9D C5

 */

int readkbd()
{
    unsigned int c = 0;
    unsigned char d;
    if (kbd_bfr_ready == FALSE)
        return -1;

    if (ring_data_get(&kbd_scn_rng, (char *)&c, sizeof(c)) == 0)
        return -1;

    if (ring_data_get(&kbd_asc_rng, (char *)&d, sizeof(d)) == 0)
        return -1;

    c = c << 16 | d;

    return (int)c;
}

static void movebuf(void * ptr, size_t n, size_t c)
{
    memcpy(ptr, (char *)ptr + c, n - c);
    memset(ptr + n - c, 0, c);
}

static void wait_input_empty()
{
    char d;

    in_b(KBD_8042_PORT2, d);

    while (d & KBD_8042_IN_F)
        asm_delay();

}

static void wait_output_empty()
{
    char d;

    in_b(KBD_8042_PORT2, d);

    while (d & KBD_8042_OUT_F)
        {
        in_b(KBD_8042_PORT1, d);

        in_b(KBD_8042_PORT2, d);

        asm_delay();
        }
}

static void out_to_8048(char c)
{
    out_b(KBD_8042_PORT1, c);

    return;
}

static void kbd_update_led()
{
    out_to_8048(KBD_8048_SETLED);

    asm_delay();

    out_to_8048(kbd_led_flag);

    asm_delay();
}

static unsigned char kbd_trans_asc(unsigned char c, unsigned char d)
{
    if (isalpha(c))
        {
        /* ctrl key */

        if (kbd_flag & KBD_CTRL_BIT)
            c = tolower(c) - 'a' + 1;
        else
            {
            if (kbd_led_flag & KBD_LED_CAPS)
                {
                if (kbd_flag & KBD_SHIFT_BIT)
                    c = tolower(c);
                else
                    c = toupper(c);
                }
            }
        }
    else if ((kbd_flag & KBD_CTRL_BIT) || (kbd_flag && KBD_ALT_BIT))
        {
        c = KEY_NO_ASC;
        }
    else
        {
        if (isdigit(c) || c == '.')
            {
            if (!(kbd_led_flag & KBD_LED_NUM))
                {
                if (d > 0x10)
                    c = num_map[c - '.'];
                }
            }
        }

    return c;

}

static void kbd_verifyscan()
{
    unsigned int ts, selector, bkflag, ledflag;
    unsigned char c, d, emflag;
    static unsigned char led_delay_flag;

    while ((d = tscan[0]) != 0)
        {
        selector = 0;
        emflag = 0;
        bkflag = 0;
        ledflag = 0;

        ts = d;

        if (d >= 0x60)
            {
            if (d == 0xE1)
                {
                if (tscan[1] == 0x1D
                    && tscan[2] == 0x45
                    && tscan[3] == 0xE1
                    && tscan[4] == 0x9D
                    && tscan[5] == 0xC5)
                    {
                    ts = KEY_VSCAN_BREAK;
                    c = 0;
                    ring_data_put(&kbd_scn_rng, (char *)&ts, sizeof(ts));
                    ring_data_put(&kbd_asc_rng, (char *)&c, sizeof(c));

                    kbd_bfr_ready = TRUE;

                    movebuf(tscan, KBD_BFR_SIZE, 6);

                    continue;
                    }
                return;
                }
            else if (d == 0xE0)
                {
                if (tscan[1] == 0)
                    return;

                selector++;
                movebuf(tscan, KBD_BFR_SIZE, 1);
                d = tscan[0];

                ts = 0xE000 + d;

                if (d >= 0x80 && d < 0xE0)
                    {
                    bkflag = 1;
                    d = d - 0x80;
                    }
                }
            else if (d >= 0x80 && d < 0xE0)
                {
                bkflag = 1;
                d = d - 0x80;
                }
            else
                {
                movebuf(tscan, KBD_BFR_SIZE, 1);
                continue;
                }
            }

        if (kbd_flag & KBD_SHIFT_BIT)
            selector += 2;

        c = US_MAP[selector][d];

        switch(c)
            {
            case KEY_L_SHIFT:
            case KEY_R_SHIFT:
                emflag = KBD_SHIFT_BIT;
                break;
            case KEY_L_ALT:
            case KEY_R_ALT:
                emflag = KBD_ALT_BIT;
                break;
            case KEY_L_CTRL:
            case KEY_R_CTRL:
                emflag = KBD_CTRL_BIT;
                break;
            case KEY_CAPS_LOCK:
                emflag = KBD_LED_CAPS;
                ledflag = 1;
                break;
            case KEY_NUM_LOCK:
                emflag = KBD_LED_NUM;
                ledflag = 1;
                break;
            case KEY_SCR_LOCK:
                emflag = KBD_LED_SCROLL;
                ledflag = 1;
                break;
            default:
                break;
            }

        if (ledflag)
            {
            if (bkflag)
                led_delay_flag &= ~emflag;
            else
                {
                /* ignore lock key repeat */
                if (!(led_delay_flag & emflag))
                    {
                    led_delay_flag |= emflag;

                    if (kbd_led_flag & emflag)
                        kbd_led_flag &= ~emflag;
                    else
                        kbd_led_flag |= emflag;

                    kbd_update_led();

                    asm_delay();
                    }
                }
            }
        else if (emflag == 0 && bkflag == 0 && c != KEY_NONE)
            {
            c = kbd_trans_asc(c, d);
            if (c != KEY_NONE)
                {
                ring_data_put(&kbd_scn_rng, (char *)&ts, sizeof(ts));
                ring_data_put(&kbd_asc_rng, (char *)&c, sizeof(c));
                kbd_bfr_ready = TRUE;
                }
            }
        else if (emflag != 0)
            {
            if (bkflag == 0)
                kbd_flag |= emflag;
            else
                kbd_flag &= ~emflag;
            }

        movebuf(tscan, KBD_BFR_SIZE, 1);
        }
}

static void kbd_int_routine(int int_num)
{
    char d;
    int i;

    if (int_num != KBD_INT_NUM)
        goto EXIT;

    in_b(KBD_8042_PORT1, d);

    if (d == (char)0xFA)
        {
        goto EXIT;
        }

    for (i = 0; i < KBD_BFR_SIZE; i++)
        {
        if (tscan[i] == 0)
            {
            tscan[i] = d;
            break;
            }
        }

    if (i == KBD_BFR_SIZE)
        goto EXIT;

    kbd_verifyscan();

EXIT:
    if (eoi_ptr != NULL)
        eoi_ptr();

    return;
}

void disable_kdb()
{
    wait_input_empty();

    out_b(KBD_8042_PORT2, (char)KBD_8042_DISABLE);
}

void enable_kdb()
{
    wait_input_empty();

    out_b(KBD_8042_PORT2, (char)KBD_8042_ENABLE);
}

void init_kbd()
{
    unsigned short cs;

    disable_kdb();

    /* wait for 8042 buffer empty */

    wait_output_empty();

    kbd_asc_rng.p_buf = kbd_buf_asc;
    kbd_asc_rng.size = sizeof(kbd_buf_asc) - sizeof(char);

    ring_empty(&kbd_asc_rng);

    kbd_scn_rng.p_buf = (char *)kbd_buf_scn;
    kbd_scn_rng.size = sizeof(kbd_buf_scn) - sizeof(int);

    ring_empty(&kbd_asc_rng);

    kbd_led_flag = KBD_LED_NUM;

    kbd_update_led();

    wait_output_empty();

    enable_kdb();

    cs = GetCurrentCodeSeg();

    install_vitual_int(KBD_INT_NUM, (int)kbd_int_routine);
}

